#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
Question:
WHEN does Consumer born?
    Consumer should born as early as possile
    Consumer should be exist independent of Channel
    Consumer should be enrolled by Dispatcher

WHAT does Context contain?
    Context should provide what the Consumer wants
    Context should be consist among the life-cycle of all consumer

WHOM is the record parsed by? by Dispatcher? by Consumer?
    what is the record? a packet? a message??
    Dispatcher would parse the record, or how to known which Consumer is the record dispatched to?
    Dispatcher can parse some ROUTING information from the record, but it can NOT understand APP data.
    so, Consumer will parse the record (or part of the record) again, to get APP data.
    packet ::= routing + data
    routing ::= information about where packet comes, where packet goes
    data ::= application data

WHAT is the mechanism of routing executed by Dispatcher?

WHAT does the Consumer do with Dispatcher in feed() ?

HOW do the Dispatcher dispatch a RESPONSE packet ?

'''

import os, sys, signal
import asyncore, asynchat, socket, threading, time
import base64

HOST = "127.0.0.1"
PORT = 54344

def DEBUG(format, *args) : print format % args

will_stop = False

def signal_stop( signo, frame ) :
    global  will_stop
    DEBUG( "SERVER catch #%d signal, would stop.", signo )
    will_stop = True
    return
    # signal_stop


#######################################################################

def enpackResponse(service, body) :
    msg = "action:%s\tservice:%s\tbody:%s" % ('RESPONSE', service, base64.b64encode(body))
    return msg

class EchoConsumer(object)  :
    '''Echo Consumer'''
    def feed(self, service, channel_from, record, dispatcher) :
        record = base64.b64decode(record)
        DEBUG('COMSUMER echo: %s', record)
        msg = enpackResponse(service, record)
        channel_from.push(msg + '\n')
        # feed()
    # class Consumer


class Context(object)   :
    '''Context -- context for Consumer'''
    def __init__(self, dispatcher) :
        self.dispatcher = dispatcher
        # __init__()
    # class Context

class Dispatcher(object)    :
    '''Dispatcher -- dispatch message to consumer'''
    def __init__(self) :
        # table-channel : address --> channel
        self.__table_channel = {}
        # table-consumer : SERVICE --> consumer
        self.__table_consumer = {}
        # __init__()

    class   Packet(object)  :
        def __init__(self) :
            self.action = None
            self.service = None
            self.body = None
            # __init__()

        def isComplete(self) :
            if not self.action or not self.service or not self.body :
                return  False
            return  True

        def isRequest(self) :
            if 'REQUEST' == self.action : return True
            return  False
        # class Packet

    def forward(self, channel_from, record) :
        '''forward a record to a channel
        '''
        pass
        # forward()

    def dispatch(self, channel_from, record) :
        '''dispatch a record to a consumer

        List of Actions
         * parse record to get address info (such as SERVICE name)
         * lookup route-table to get the Consumer who is responsible for the record,
            if No Consumer is responsible for it :
                discard the message(record)
                return False
            OR
            if No Consumer is responsible for it :
                use Router as Consumer (route the message again)
         * feed message to consumer
         * return True
        '''
        # parse record, to get SERVICE
        packet = self.depack(record)
        if not packet : raise RuntimeError, 'invalid record: %s' % record
        if not packet.isRequest() : raise RuntimeError, 'cannot handle %s action' % packet.action

        service = packet.service
        # find Consumer for the SERVICE
        if service not in self.__table_consumer : return  False
        #    for each SERVICE, organize Consumers as a list, we just use the first consumer
        consumer = self.__table_consumer[service][0]

        # for ECHO service, channel_to === channel_from
        consumer.feed(packet.service, channel_from, packet.body, self)

        return  True
        # dispatch()

    def depack(self, record) :
        '''decode packet record into Packet struct'''
        field_separator = '\t'
        kv_separator = ':'
        packet = self.Packet()
        fields = record.split(field_separator)
        for f in fields :
            (key, value) = f.split(kv_separator, 1)
            if not value : continue
            if 'action' == key :
                packet.action = value
            elif 'service' == key :
                packet.service = value
            elif 'body' == key :
                packet.body = value

        if not packet.isComplete() : return None
        return  packet
        # depack()

    def registerConsumer(self, service, consumer) :
        if not service : raise RuntimeError, 'Dispatcher: invalid service to register'
        if not consumer : raise RuntimeError, 'Dispatcher: invalid consumer to register'
        if service in self.__table_consumer :
            self.__table_consumer[service].append(consumer)
        else :
            self.__table_consumer[service] = [consumer,]
        # registerConsumer()
    def unregisterConsumer(self, service, consumer) :
        if not service : raise RuntimeError, 'Dispatcher: invalid service to register'
        if not consumer : raise RuntimeError, 'Dispatcher: invalid consumer to register'
        if service in self.__table_consumer :
            self.__table_consumer[service].remove(consumer)
        # unregisterConsumer()
    # class Dispatcher

#######################################################################

class EchoChannel(asynchat.async_chat) :
    def    __init__(self, connect, address, context) :
        asynchat.async_chat.__init__(self, conn=connect)
        self.__connect = connect
        self.__address = address
        self.__buf_incoming = []
        self.__terminator = '\n'
        self.set_terminator(self.__terminator)

        self.__context = context
        # __init__()

    def    collect_incoming_data(self, data) :
        DEBUG('SERVER collect: >>>%s<<<', data)
        self.__buf_incoming.append(data)
        # collect_incoming_data()

    def    found_terminator(self) :
        DEBUG('SERVER found: >>>%s<<<', self.__buf_incoming)
        line = ''.join(self.__buf_incoming)
        self.__buf_incoming = []

        self.__context.dispatcher.dispatch(self, line)
        # found_terminator()

    def    handle_close(self) :
        DEBUG('SERVER close connection for client "%s".', self.__address)
        self.close()
        # handle_close()

    # class EchoChannel

class EchoServer(asyncore.dispatcher):

    def __init__(self, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.bind(("", port))
        self.listen(5)
        DEBUG('SERVER listening on PORT:%d ...', port)

        dispatcher = Dispatcher()
        consumer = EchoConsumer()
        dispatcher.registerConsumer('ECHO', consumer)

        context = Context(dispatcher)
        self.__context = context
        # __init__()

    def handle_accept(self) :
        connect, client = self.accept()
        DEBUG('SERVER accept client %s', client)

        EchoChannel(connect, client, self.__context)
        # handle_accept()

    # class EchoServer

def main():
    # mask signal
    DEBUG('os.name: %s', os.name)
    signal.signal( signal.SIGTERM, signal_stop )
    if 'posix' == os.name :
        signal.signal( signal.SIGQUIT, signal_stop )

    s = EchoServer(PORT)
    try :
        asyncore.loop()
    except :
        DEBUG( "SERVER interrupt : %s", `sys.exc_info()[1]` )

if __name__ == '__main__' :
    main()
    sys.exit(0)
